package com.imooc.filter;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.google.gson.Gson;
import com.imooc.base.BaseInfoProperties;
import com.imooc.result.GraceJSONResult;
import com.imooc.result.ResponseStatusEnum;
import com.imooc.utils.JwtUtils;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 过滤器
 * @author liansen
 * @create 01-14-17:39
 *
 * 在网关中对请求路径进行拦截, 校验jwt, 看用户有没有登录, 登录了再放行;
 *
 * (发送短信验证码, 登录, 退出)这三个请求不拦截
 * /passport/getSMSCode;
 * /passport/login
 * /passport/logout
 *
 * gateway中有两个过滤器，gatewayFilter, GlobalFilter(全局过滤器)
 * 实现Ordered是为了设置过滤器的执行顺序, 如果有多个过滤器，设置的数字越小，过滤器执行的优先级越高
 */
@Component
@Slf4j
public class SecurityFilterJWT extends BaseInfoProperties implements GlobalFilter, Ordered {

    @Autowired
    private ExcludeUrlProperties excludeUrlProperties;

    //spring中的路径规则匹配器
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private JwtUtils jwtUtils;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //获取前端传来的当前的请求路径
        String path = exchange.getRequest().getURI().getPath();
        log.info("当前的请求路径 " + path);

        /**
         * 判断前端传来的请求路径是不是(发送短信验证码, 登录, 退出)这三个请求,如果是, 直接放行
         * 将这三个请求放在配置文件中,在程序中获取
         */

        //获取配置文件中不需要被拦截的请求路径
        List<String> excludeList = excludeUrlProperties.getUrls();

        if(!CollectionUtils.isEmpty(excludeList)){
            for (String excludeUrl : excludeList) {
                /**
                 * match中两个参数  match(String pattern, String path)
                 * pattern: 规则,也就是不需要被排除的请求路径
                 * path: 前端传来的请求路径
                 */
                if(antPathMatcher.match(excludeUrl, path)){
                    //放行
                    return chain.filter(exchange);
                }
            }
        }

        //到达此处表示被拦截
        log.info("被jwt过滤器拦截住的路径:" + path);
        //GraceException.display(ResponseStatusEnum.UN_LOGIN);

        //获取请求中的jwtToken并校验
        HttpHeaders headers = exchange.getRequest().getHeaders();
        //判断header中有没有token   headerUserToken是前端的token名称
        //因为创建jwt的时候，返回给前端的是 prefix@jwttoken, 所以要切割一下;
        String userToken = headers.getFirst("headerUserToken");
        if(StringUtils.isBlank(userToken)){
            return rebderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);
        }
        String[] tokens = userToken.split(JwtUtils.at);
        String prefix = tokens[0];
        String token = tokens[1];
        if(prefix.equalsIgnoreCase(TOKEN_USER_PREFIX)){
            return dealJwt(token, exchange, chain, APP_USER_JSON);
        }else if (prefix.equalsIgnoreCase(TOKEN_SAAS_PREFIX)){
            return dealJwt(token, exchange, chain, SAAS_USER_JSON);
        }else if(prefix.equalsIgnoreCase(TOKEN_ADMIN_PREFIX)){
            return dealJwt(token, exchange, chain, ADMIN_USER_JSON);
        }
        //处理jwt token, 如果jwt校验不通过, 则不放行, 返回一个错误信息
//        return dealJwt(token, exchange, chain, APP_USER_JSON);
        return rebderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);
    }

    //渲染错误信息
    public Mono<Void> rebderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum responseStatusEnum){
        //获取response
        ServerHttpResponse response = exchange.getResponse();
        //构建jsonResult, 现在jsonResult是java对象
        GraceJSONResult jsonResult = GraceJSONResult.exception(responseStatusEnum);
        //设置状态码为500
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        //设置返回json类型
        // 设置header类型为 Content-type: application/json
        if(!response.getHeaders().containsKey("Content-type")){
            response.getHeaders().add("Content-type", MimeTypeUtils.APPLICATION_JSON_VALUE);
        }
        //转换json字符串, 并向response中写入数据
        String jsonResultString = new Gson().toJson(jsonResult);
        DataBuffer buffer = response
                                .bufferFactory()
                                .wrap(jsonResultString.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    //过滤器的顺序, 数字越小优先级越高
    @Override
    public int getOrder() {
        return 0;
    }

    public Mono<Void> dealJwt(String token, ServerWebExchange exchange, GatewayFilterChain chain, String headerKey){
        /**
         * 当初创建jwt的时候，我们加入了过期时间, 所以这里记得要
         * 处理jwt过期的异常, 之前在网关中无法使用全局异常处理，
         * 所以这里用try catch捕获, 然后手动渲染错误信息;
         */
        try{
            //获取到了token中的用户信息
            String userJson = jwtUtils.checkJwtToken(token);

            log.info("userJson" +  userJson);
            ServerWebExchange serverWebExchange = setNewHeader(exchange, headerKey, userJson);
            //使用新构造的exchange
            return chain.filter(serverWebExchange);
//            return chain.filter(exchange);
        }catch(ExpiredJwtException e){
            e.printStackTrace();
          //登录有效期已过，请重新登录
            return rebderErrorMsg(exchange, ResponseStatusEnum.JWT_EXPIRE_ERROR);
        }catch(Exception e){
            e.printStackTrace();
            return rebderErrorMsg(exchange, ResponseStatusEnum.JWT_SIGNATURE_ERROR);
        }
    }

    /*
        exchange可以设置后续的请求和响应，所以设置一个新的exchange, 从exchange中获取到request,
        将用户信息放进header中，key是传来的参数，值是用户信息，构建一个新的request,
        最后返回新的exchange;
     */
    public ServerWebExchange setNewHeader(ServerWebExchange exchange, String headerKey, String headerValue){

        //构建新的request
        ServerHttpRequest newRequest = exchange.getRequest()
                                                .mutate()
                                                .header(headerKey, headerValue)
                                                .build();
        //替换原来的request
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

        return newExchange;
    }

}
